﻿using System;
using System.Collections.Generic;
using KryptecizerBase;
using Kryptonalysis.BletchleyPark;
using Kryptonalysis.Statistics;
using Kryptonalysis.EventArguments;
using System.Collections;
using System.Threading;

namespace Kryptonalysis
{
    public class VigenereAnalyzer : TextBasedAnalyzer
    {
        private const int minGCD = 0;
        private double percentsDone;
        private double percentsDoneOld;
        private double distanceTablepercent;
        private const int maxLetterCombiLength = 10;
        private const int minLetterCombiLength = 5;

        /// <summary>
        /// Method to detect the password.
        /// </summary>
        /// <param name="kryptoText">The krypto text.</param>
        /// <returns>the password</returns>
        protected override string DetectPassword(KryptoText kryptoText)
        {
            stats = new AnalysisStatistic();
            percentsDone = 0;
            List<int> GCDs = ReduceGCDs(DeterminGCDs(DeterminPasswordLengths(kryptoText)));
            int gdc = GetFirstGCD(GCDs);
            String[] strings = DivideText(kryptoText.GetAsString(), gdc);

            string[] pws = GetCaesarAnalysedPW(strings, kryptoText);
            string pw = GetPW(pws);
            percentsDone = 100;
            Processing();
            return pw;
        }

        private void Processing()
        {
            if ((((int)percentsDone) - ((int)percentsDoneOld)) >= 1)
            {
                OnAnalysisProcessing((int)percentsDone);
                percentsDoneOld = percentsDone;
            }
        }

        /// <summary>
        /// Gets the caesar analysed password.
        /// Gets the password by using the CaearAnalyzer for each sring defined in the string-array.
        /// </summary>
        /// <param name="strings">The strings.</param>
        /// <param name="ktext">The ktext.</param>
        /// <returns>the passwords for all strings</returns>
        private string[] GetCaesarAnalysedPW(string[] strings, KryptoText ktext)
        {
            string[] pws = new string[strings.Length];

            CaesarAnalyzer ca = new CaesarAnalyzer();
            for (int i = 0; i < strings.Length; i++)
            {
                ktext.Fill(strings[i]);
                ca.EncryptedKryptoText = ktext;
                pws[i] = ca.DetectPassword();
            }
            percentsDone += 10;
            Processing();
            return pws;
        }

        /// <summary>
        /// Gets the first GCD.
        /// </summary>
        /// <param name="GCDs">The GC ds.</param>
        /// <returns>first GCD</returns>
        private int GetFirstGCD(List<int> GCDs)
        {
            int gcd = 0;
            if(GCDs.Count > 0)
                gcd = GCDs[0];
            
            if (gcd <= 0)
                gcd = 1;
            percentsDone += 2;
            Processing();
            return gcd;
        }

        /// <summary>
        /// Gets the password.
        /// Combines each pw of array to the pasword.
        /// It is highly recommanded, that each string in array 
        /// should only hava a length of one letter.
        /// </summary>
        /// <param name="pws">The PWS.</param>
        /// <returns>the password</returns>
        private string GetPW(string[] pws)
        {
            string result = String.Empty;
            foreach (string pw in pws)
            {
                int tmp = Convert.ToInt32(pw);
                if(tmp < 0)
                    result += Convert.ToChar(123 + tmp);
                else
                    result += Convert.ToChar(97 + tmp);
            }
            if (result.Length == 2)
                if (result[0] == result[1])
                    result = result[0].ToString();
            percentsDone += 2;
            Processing();
            return result;
        }

        /// <summary>
        /// Reduces the GCDs recursivly to the both most likely GCDs o the list.
        /// </summary>
        /// <param name="GCDs">The GC ds.</param>
        /// <returns>both most likely GCDs</returns>
        private List<int> ReduceGCDs(List<int> GCDs)
        {
            List<int> vals = GCDs;
            int gdc = 0;
            List<int> result = new List<int>();
            if (vals.Count > 1)
            {
                if (vals.Count > 2)
                {
                    for (int i = 0; i < vals.Count - 2; i += 2)
                    {
                        if (vals[i] == vals[i + 1])
                            gdc = vals[i];
                        else
                            gdc = GCD.ggT(vals[i], vals[i + 1]);
                        if (gdc > minGCD)
                            result.Add(gdc);
                    }
                    if (!((vals.Count % 2) == 0))
                        result.Add(vals[vals.Count - 1]);
                }
                else if(vals.Count == 2)
                {
                    if (vals[0] == vals[1])
                        gdc = vals[0];
                    else
                        gdc = GCD.ggT(vals[0], vals[1]);
                    if (gdc > minGCD)
                        result.Add(gdc);
                }
                if(result.Count >= 2)
                   result = ReduceGCDs(result);
            }
            percentsDone += 5;
            Processing();
            return result;
        }

        /// <summary>
        /// Determins the GCDs of an interger-list.
        /// </summary>
        /// <param name="lengths">The lengths.</param>
        /// <returns>List of GCDs</returns>
        private List<int> DeterminGCDs(List<int> lengths)
        {
            List<int> result = new List<int>();
            int gdc = 0;
            if (lengths.Count > 2)
            {
                for (int i = 0; i < lengths.Count - 2; i++)
                {
                    gdc = GCD.ggT(lengths[i], lengths[i + 1]);
                    if (gdc > minGCD)
                        result.Add(gdc);
                }
            }
            if (lengths.Count > 1)
            {
                gdc = GCD.ggT(lengths[0], lengths[lengths.Count - 1]);
                if (gdc > minGCD)
                    result.Add(gdc);
            }
            percentsDone += 5;
            Processing();
            SetGCDStat(result);
            return result;
        }

        /// <summary>
        /// Sets the GCD statistic.
        /// </summary>
        /// <param name="GCDs">The GC ds.</param>
        private void SetGCDStat(List<int> GCDs)
        {
            SortedList<int, int> list = new SortedList<int, int>();
            foreach (int item in GCDs)
            {
                if (!list.ContainsKey(item))
                    list[item] = 1;
                else
                    list[item]++;
            }
            stats.Dividors = list;
            percentsDone += 5;
            Processing();
        }

        /// <summary>
        /// Determins the possible password lengths.
        /// </summary>
        /// <param name="kryptoText">The krypto text.</param>
        /// <returns>List of possible passwordlengths.</returns>
        private List<int> DeterminPasswordLengths(KryptoText kryptoText)
        {
            double onePercent = ((double)100d / (maxLetterCombiLength - minLetterCombiLength));
            distanceTablepercent = onePercent / 10000d * 0.7;
            stats.Offsets = new SortedList<int, SortedList<int, int>>();
            List<int> keys, lengths = new List<int>();
            for (int i = maxLetterCombiLength; i >= minLetterCombiLength; i--)
            {
                StringDistanceTable distt = new StringDistanceTable(i, kryptoText, new StringDistanceTable.ProcessingEventHandler(distt_Processing));
                keys = distt.Evalaute();
                stats.Offsets.Add(i, distt.DistanceTable);
                
                foreach (int item in keys)
                    lengths.Add(item);
                //percentsDone += (int)onePercent;// (20d * 0.7);// (int)(onePercent * (maxLetterCombiLength - i + 1));
                //OnAnalysisProcessing(percentsDone);
            }

            return lengths;
        }


        /// <summary>
        /// Method to handle the processing event of the StringDistanceTable
        /// </summary>
        /// <param name="percentDone">The percent done.</param>
        void distt_Processing(int percentDone)
        {
            percentsDone += distanceTablepercent;
            Processing();
        }

        /// <summary>
        /// Divides the text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="assumedPWLength">Length of the assumed PW.</param>
        /// <returns>divided text</returns>
        private String[] DivideText(string text, int assumedPWLength)
        {
            String[] result = new String[assumedPWLength];
            text = KryptoText.EleminateSpecialSigns(text);
            int index = 0;
            for (int i = 0; i < text.Length; i++)
            {
                result[index] += text[i];
                index++;
                if(index >= assumedPWLength)
                    index = 0;
            }
            percentsDone += 1;
            Processing();
            return result;
        }
    }
}
